/*
 * Copyright 2009 Yannick Stucki (yannickstucki.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.yannickstucki.android.musicqueue;

import java.util.LinkedList;

import android.content.res.Resources;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.LayoutAnimationController;
import android.view.animation.TranslateAnimation;
import android.widget.AbsoluteLayout;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AbsoluteLayout.LayoutParams;
import android.widget.SeekBar.OnSeekBarChangeListener;

/**
 * Handles the player UI in the portrait mode.
 * 
 * TODO: there is still some logic in here and some portrait specifc stuff in
 * {@link MusicQueue}.
 * 
 * @author Yannick Stucki (yannickstucki@gmail.com)
 */
@SuppressWarnings("deprecation")
public class PlayerUiPortrait {

  /**
   * The x or y offset in pixel that has to be crossed so that a touch is
   * recognized as a move.
   */
  private static final int MOVE_TOLERANCE = 50;

  /**
   * The x offset in pixels that has to be added when a user clicks on the list.
   */
  private static final int XOFFSET = 52;

  /**
   * The y offset in pixels that has to be added when a user clicks on the list.
   */
  private static final int YOFFSET = 14;

  /**
   * The animation delay of every child on shuffle.
   */
  private static final float SHUFFLE_CHILD_ANIMATION_DELAY = 0.5f;

  /**
   * The animation duration for the first phase of the shuffle.
   */
  private static final int SHUFFLE_ANIMATION_DURATION_1 = 100;

  /**
   * The animation duration for the second phase of the shuffle.
   */
  private static final int SHUFFLE_ANIMATION_DURATION_2 = 200;

  /**
   * If touch movement on the list has an offset in x or y direction of greater
   * or equal to this, the gesture doesn't count as a click.
   */
  private static final int CLICK_PIXEL_TOLERANCE = 10;

  /**
   * The title of the song that is hovering under the finger if the user is
   * dragging.
   * 
   * TODO: absolute view is deprecated, use something else.
   */
  private TextView hoveringSong;

  // private View hoveringView;

  /**
   * The time elapsed while playing this song.
   */
  private TextView elapsedTime;

  /**
   * The total duration of this song.
   */
  public TextView duration;

  /**
   * The seekbar signals progress and is used to jump to any position of the
   * song.
   */
  public SeekBar seekBar;

  /**
   * The elapsed time in milliseconds.
   */
  private int elapsedMilliseconds = 0;

  /**
   * The button displaying the size of the queue. On click a Toast with the
   * content of the queue is displayed.
   */
  private Button queueSizeButton;

  /**
   * Replaces all the songs (except the playing one) in the list.
   */
  private ImageButton shuffleButton;

  /**
   * Toggles play / pause.
   */
  private ImageButton playPauseButton;

  /**
   * The actual list.
   */
  private ListView listView;

  /**
   * True if the user is actively seeking a new position in the song. The
   * progress updater then doesn't update the seek bar.
   */
  public boolean seeking = false;

  /**
   * The relative x and y coordinates of a child in the list.
   */
  private int relX, relY;

  /**
   * The x and y coordinates where the finger was pushed down.
   */
  private int startX, startY;

  /**
   * A reference to the song manager.
   */
  private SongManager songManager;

  /**
   * Used for the hovering song.
   * 
   * TODO: deprecated, replace
   */
  private LayoutParams absoluteLayoutParams;

  /**
   * Toasts are the little overlay texts.
   */
  private Toast toast;

  /**
   * Used as a context and to extract resources.
   */
  private MusicQueue musicQueue;

  /**
   * The constructor sets references.
   * 
   * TODO: how can we get less parameters?
   */
  public PlayerUiPortrait(TextView time, TextView duration, SeekBar seekBar,
      ImageButton playPauseButton, TextView current, Button queueSizeButton,
      ImageButton shuffleButton, ListView listView, SongManager songManager,
      Toast toast) {
    this.songManager = songManager;
    this.elapsedTime = time;
    this.duration = duration;
    this.seekBar = seekBar;

    this.playPauseButton = playPauseButton;
    this.hoveringSong = current;
    this.queueSizeButton = queueSizeButton;
    this.shuffleButton = shuffleButton;
    this.listView = listView;
    this.toast = toast;
  }

  /**
   * Initializes the UI.
   */
  public void initUi(final MusicQueue musicQueue,
      final PlayerService playerService) {

    this.musicQueue = musicQueue;

    final Resources resources = musicQueue.getResources();
    absoluteLayoutParams = new AbsoluteLayout.LayoutParams(
        AbsoluteLayout.LayoutParams.WRAP_CONTENT,
        AbsoluteLayout.LayoutParams.WRAP_CONTENT, 0, 0);

    seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
      @Override
      public void onProgressChanged(SeekBar arg0, int progress,
          boolean fromTouch) {
        // TODO: fromTouch -> trackball
        if (fromTouch) {
          elapsedMilliseconds = progress;
        }
        // update the progress time
        progress /= Helper.MILLISECONDS_IN_SECOND;
        int mins = progress / Helper.SECONDS_IN_MINUTE;
        progress -= mins * Helper.SECONDS_IN_MINUTE;
        elapsedTime.setText(Helper.getTime(mins, progress));
      }

      @Override
      public void onStartTrackingTouch(SeekBar arg0) {
        seeking = true;
      }

      @Override
      public void onStopTrackingTouch(SeekBar arg0) {
        playerService.seekTo(elapsedMilliseconds);
        seeking = false;
      }
    });

    playPauseButton.setImageResource(R.drawable.ic_media_play);
    if (playerService != null) {
      if (playerService.isPlaying()) {
        playPauseButton.setImageResource(R.drawable.ic_media_pause);
      }
    }

    playPauseButton.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        playerService.playPause();
      }
    });

    if (songManager != null) {
      setQueueSize(songManager.waitingQueue.size());
    }

    queueSizeButton.setOnClickListener(new OnClickListener() {

      @Override
      public void onClick(View v) { // display the contents of the
        // queue
        String toShow;
        int waitingQueueSize = songManager.waitingQueue.size();
        if (waitingQueueSize == 0) {
          toShow = resources.getString(R.string.no_enqueued_songs);
        } else if (waitingQueueSize == 1) {
          toShow = waitingQueueSize + " "
              + resources.getString(R.string.single_song_enqueued);
        } else {
          toShow = waitingQueueSize + " "
              + resources.getString(R.string.songs_enqueued);
        }

        LinkedList<Song> waitingQueue = songManager.waitingQueue;

        for (int i = 0; i < waitingQueueSize; i++) {
          toShow += "\n" + waitingQueue.get(i).title;
        }

        if (toast != null) {
          toast.cancel();
        }
        toast = Toast.makeText(musicQueue, toShow, Toast.LENGTH_LONG);
        toast.show();
      }
    });

    shuffleButton.setImageResource(R.drawable.ic_popup_sync_3);

    shuffleButton.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        shuffle();
      }
    });

    // intercept touch events on the list
    listView.setOnTouchListener(new OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event) {
        listViewOnTouch(event);
        return true; // intercept: so return true
      }
    });
  }

  /**
   * Handles all the dragging of the list items.
   * 
   * TODO: maybe outsource that to another class?
   */
  public void listViewOnTouch(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();

    int position = listView.pointToPosition((int) event.getX(), (int) event
        .getY());

    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
      Logger.out("ACTION_DOWN");
      if (position < 0 || position >= SongManager.COUNT) {
        return;
      }
      startX = x;
      startY = y;

      View child = listView.getChildAt(position);
      relX = x - child.getLeft();
      relY = y - child.getTop();

      absoluteLayoutParams.x = x - relX + XOFFSET;
      absoluteLayoutParams.y = y - relY + YOFFSET;

      hoveringSong.setLayoutParams(absoluteLayoutParams);
      hoveringSong
          .setText(songManager.songs[position].displayStrings[Song.DISPLAY_INDEX_PORTRAIT]);

      songManager.down(position);

      break;
    /*
     * case MotionEvent.ACTION_CANCEL: break;
     */
    case MotionEvent.ACTION_UP:
      if (Math.abs(x - startX) < CLICK_PIXEL_TOLERANCE
          && Math.abs(y - startY) < CLICK_PIXEL_TOLERANCE) {
        songClicked(position);
      }

      songManager.up();
      if (Math.abs(y - startY) < MOVE_TOLERANCE
          && Math.abs(x - startX) > MOVE_TOLERANCE) {
        songManager.remove(position);
      }
      hoveringSong.setText("");
      break;

    case MotionEvent.ACTION_MOVE:
      if (position < 0 || position >= SongManager.COUNT) {
        return;
      }
      absoluteLayoutParams.x = x - relX + XOFFSET;
      absoluteLayoutParams.y = y - relY + YOFFSET;
      hoveringSong.setLayoutParams(absoluteLayoutParams);
      songManager.move(position);
      break;
    default:
      // Some action event we are not interested in.
      break;
    }
  }

  /**
   * When a song gets clicked in portrait more information about the song is
   * displayed.
   */
  public void songClicked(int position) {
    Logger.out("Clicked on position: " + position);
    if (position != -1) {
      if (songManager.songs[position] != null) {
        if (toast != null) {
          toast.cancel();
        }
        toast = Toast.makeText(musicQueue, songManager.songs[position].title
            + "\n" + songManager.songs[position].artist + "\n"
            + songManager.songs[position].album + "\n", Toast.LENGTH_SHORT);
        toast.show();
      }
    }
  }

  /**
   * Performs the shuffle. All songs except the top song are being replaced.
   */
  private void shuffle() {
    AnimationSet set = new AnimationSet(true);
    Animation animation = new AlphaAnimation(0.0f, 1.0f);
    animation.setDuration(SHUFFLE_ANIMATION_DURATION_1);
    set.addAnimation(animation);

    animation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f,
        Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f,
        Animation.RELATIVE_TO_SELF, 0.0f);
    animation.setDuration(SHUFFLE_ANIMATION_DURATION_2);
    set.addAnimation(animation);

    LayoutAnimationController controller = new LayoutAnimationController(set,
        SHUFFLE_CHILD_ANIMATION_DELAY);

    listView.setLayoutAnimation(controller);
    songManager.shuffle();
  }

  /**
   * Sets the number displayed on the button representing the queue size.
   */
  public void setQueueSize(int number) {
    queueSizeButton.setText(Integer.toString(number));
  }

  /**
   * Displays the play image (>) on the play/pause button.
   */
  public void setImageToPlay() {
    playPauseButton.setImageResource(R.drawable.ic_media_play);
  }

  /**
   * Displays the pause image (II) on the play/pause button.
   */
  public void setImageToPause() {
    playPauseButton.setImageResource(R.drawable.ic_media_pause);
  }

  /**
   * Shows the UI (because songs were found).
   */
  public void setUiToVisible() {
    seekBar.setVisibility(View.VISIBLE);
    playPauseButton.setVisibility(View.VISIBLE);
    shuffleButton.setVisibility(View.VISIBLE);
    queueSizeButton.setVisibility(View.VISIBLE);
  }

  /**
   * Hides the UI (if no songs can be found).
   */
  public void setUiToInvisible() {
    seekBar.setVisibility(View.INVISIBLE);
    playPauseButton.setVisibility(View.INVISIBLE);
    shuffleButton.setVisibility(View.INVISIBLE);
    queueSizeButton.setVisibility(View.INVISIBLE);
  }
}
